using System;
using System.Threading;
using ch.ethz.ssh2.packets;
using ch.ethz.ssh2.transport;
using ch.ethz.ssh2.log;
namespace ch.ethz.ssh2.channel
{

    /// <summary> ChannelManager. Please read the comments in Channel.java.
    /// <p>
    /// Besides the crypto part, this is the core of the library.
    /// 
    /// </summary>
    /// <author>  Christian Plattner
    /// </author>
    /// <version>  2.50, 03/15/10
    /// </version>
    public class ChannelManager : MessageHandler
    {
        //UPGRADE_NOTE: Final was removed from the declaration of 'log '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
        //UPGRADE_NOTE: The initialization of  'log' was moved to static method 'ch.ethz.ssh2.channel.ChannelManager'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1005'"
        private static readonly Logger log;

        //UPGRADE_TODO: Class 'java.util.HashMap' was converted to 'System.Collections.Hashtable' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilHashMap'"
        private System.Collections.Hashtable x11_magic_cookies = new System.Collections.Hashtable();

        private TransportManager tm;

        private System.Collections.ArrayList channels = System.Collections.ArrayList.Synchronized(new System.Collections.ArrayList(10));
        private int nextLocalChannel = 100;
        private bool shutdown = false;
        private int globalSuccessCounter = 0;
        private int globalFailedCounter = 0;

        //UPGRADE_TODO: Class 'java.util.HashMap' was converted to 'System.Collections.Hashtable' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilHashMap'"
        private System.Collections.Hashtable remoteForwardings = new System.Collections.Hashtable();

        private System.Collections.ArrayList listenerThreads = System.Collections.ArrayList.Synchronized(new System.Collections.ArrayList(10));

        private bool listenerThreadsAllowed = true;

        public ChannelManager(TransportManager tm)
        {
            this.tm = tm;
            tm.registerMessageHandler(this, 80, 100);
        }

        private Channel getChannel(int id)
        {
            //lock (channels.SyncRoot)
            lock (channels)
            {
                for (int i = 0; i < channels.Count; i++)
                {
                    Channel channel = (Channel)channels[i];
                    if (channel.localID == id)
                        return channel;
                }
            }
            return null;
        }

        private void removeChannel(int id)
        {
            //lock (channels.SyncRoot)
            lock (channels)
            {
                for (int i = 0; i < channels.Count; i++)
                {
                    Channel channel = (Channel)channels[i];
                    if (channel.localID == id)
                    {
                        channels.RemoveAt(i);
                        break;
                    }
                }
            }
        }

        private int addChannel(Channel c)
        {
            //lock (channels.SyncRoot)
            lock (channels)
            {
                channels.Add(c);
                return nextLocalChannel++;
            }
        }

        private void waitUntilChannelOpen(Channel channel)
        {
            lock (channel)
            {
                while (channel.state == Channel.STATE_OPENING)
                {
                    try
                    {
                        System.Threading.Monitor.Wait(channel);
                    }
                    catch (System.Threading.ThreadInterruptedException ignore)
                    {
                        Thread.CurrentThread.Interrupt();
                    }
                }

                if (channel.state != Channel.STATE_OPEN)
                {
                    removeChannel(channel.localID);

                    System.String detail = channel.ReasonClosed;

                    if (detail == null)
                        detail = "state: " + channel.state;

                    throw new System.IO.IOException("Could not open channel (" + detail + ")");
                }
            }
        }

        private void waitForGlobalSuccessOrFailure()
        {
            //lock (channels.SyncRoot)
            lock (channels)
            {
                while ((globalSuccessCounter == 0) && (globalFailedCounter == 0))
                {
                    if (shutdown)
                    {
                        throw new System.IO.IOException("The connection is being shutdown");
                    }

                    try
                    {
                        System.Threading.Monitor.Wait(channels);
                    }
                    catch (System.Threading.ThreadInterruptedException ignore)
                    {
                        Thread.CurrentThread.Interrupt();
                    }
                }

                if (globalFailedCounter != 0)
                {
                    throw new System.IO.IOException("The server denied the request (did you enable port forwarding?)");
                }

                if (globalSuccessCounter == 0)
                {
                    throw new System.IO.IOException("Illegal state.");
                }
            }
        }

        private void waitForChannelSuccessOrFailure(Channel channel)
        {
            lock (channel)
            {
                while ((channel.successCounter == 0) && (channel.failedCounter == 0))
                {
                    if (channel.state != Channel.STATE_OPEN)
                    {
                        System.String detail = channel.ReasonClosed;

                        if (detail == null)
                            detail = "state: " + channel.state;

                        throw new System.IO.IOException("This SSH2 channel is not open (" + detail + ")");
                    }

                    try
                    {
                        System.Threading.Monitor.Wait(channel);
                    }
                    catch (System.Threading.ThreadInterruptedException ignore)
                    {
                        Thread.CurrentThread.Interrupt();
                    }
                }

                if (channel.failedCounter != 0)
                {
                    throw new System.IO.IOException("The server denied the request.");
                }
            }
        }

        public virtual void registerX11Cookie(System.String hexFakeCookie, X11ServerData data)
        {
            //lock (x11_magic_cookies.SyncRoot)
            lock (x11_magic_cookies)
            {
                x11_magic_cookies[hexFakeCookie] = data;
            }
        }

        public virtual void unRegisterX11Cookie(System.String hexFakeCookie, bool killChannels)
        {
            if (hexFakeCookie == null)
                throw new System.SystemException("hexFakeCookie may not be null");

            //lock (x11_magic_cookies.SyncRoot)
            lock (x11_magic_cookies)
            {
                x11_magic_cookies.Remove(hexFakeCookie);
            }

            if (killChannels == false)
                return;

            if (log.Enabled)
                log.log(50, "Closing all X11 channels for the given fake cookie");

            System.Collections.ArrayList channel_copy;

            //lock (channels.SyncRoot)
            lock (channels)
            {
                channel_copy = (System.Collections.ArrayList)channels.Clone();
            }

            for (int i = 0; i < channel_copy.Count; i++)
            {
                Channel channel = (Channel)channel_copy[i];

                lock (channel)
                {
                    if (hexFakeCookie.Equals(channel.hexX11FakeCookie) == false)
                        continue;
                }

                try
                {
                    closeChannel(channel, "Closing X11 channel since the corresponding session is closing", true);
                }
                catch (System.IO.IOException e)
                {
                }
            }
        }

        public virtual X11ServerData checkX11Cookie(System.String hexFakeCookie)
        {
            //lock (x11_magic_cookies.SyncRoot)
            lock (x11_magic_cookies)
            {
                if (hexFakeCookie != null)
                {
                    //UPGRADE_TODO: Method 'java.util.HashMap.get' was converted to 'System.Collections.Hashtable.Item' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilHashMapget_javalangObject'"
                    return (X11ServerData)x11_magic_cookies[hexFakeCookie];
                }
            }
            return null;
        }

        public virtual void closeAllChannels()
        {
            if (log.Enabled)
                log.log(50, "Closing all channels");

            System.Collections.ArrayList channel_copy;

            //lock (channels.SyncRoot)
            lock (channels)
            {
                channel_copy = (System.Collections.ArrayList)channels.Clone();
            }

            for (int i = 0; i < channel_copy.Count; i++)
            {
                Channel channel = (Channel)channel_copy[i];
                try
                {
                    closeChannel(channel, "Closing all channels", true);
                }
                catch (System.IO.IOException e)
                {
                }
            }
        }

        public virtual void closeChannel(Channel channel, System.String reason, bool force)
        {
            byte[] msg = new byte[5];

            lock (channel)
            {
                if (force)
                {
                    channel.state = Channel.STATE_CLOSED;
                    channel.EOF = true;
                }

                channel.ReasonClosed = reason;

                msg[0] = (byte)(Packets.SSH_MSG_CHANNEL_CLOSE);
                msg[1] = (byte)(channel.remoteID >> 24);
                msg[2] = (byte)(channel.remoteID >> 16);
                msg[3] = (byte)(channel.remoteID >> 8);
                msg[4] = (byte)(channel.remoteID);

                System.Threading.Monitor.PulseAll(channel);
            }

            lock (channel.channelSendLock)
            {
                if (channel.closeMessageSent == true)
                    return;
                tm.sendMessage(msg);
                channel.closeMessageSent = true;
            }

            if (log.Enabled)
                log.log(50, "Sent SSH_MSG_CHANNEL_CLOSE (channel " + channel.localID + ")");
        }

        public virtual void sendEOF(Channel c)
        {
            byte[] msg = new byte[5];

            lock (c)
            {
                if (c.state != Channel.STATE_OPEN)
                    return;

                msg[0] = (byte)(Packets.SSH_MSG_CHANNEL_EOF);
                msg[1] = (byte)(c.remoteID >> 24);
                msg[2] = (byte)(c.remoteID >> 16);
                msg[3] = (byte)(c.remoteID >> 8);
                msg[4] = (byte)(c.remoteID);
            }

            lock (c.channelSendLock)
            {
                if (c.closeMessageSent == true)
                    return;
                tm.sendMessage(msg);
            }

            if (log.Enabled)
                log.log(50, "Sent EOF (Channel " + c.localID + "/" + c.remoteID + ")");
        }

        public virtual void sendOpenConfirmation(Channel c)
        {
            PacketChannelOpenConfirmation pcoc = null;

            lock (c)
            {
                if (c.state != Channel.STATE_OPENING)
                    return;

                c.state = Channel.STATE_OPEN;

                pcoc = new PacketChannelOpenConfirmation(c.remoteID, c.localID, c.localWindow, c.localMaxPacketSize);
            }

            lock (c.channelSendLock)
            {
                if (c.closeMessageSent == true)
                    return;
                tm.sendMessage(pcoc.Payload);
            }
        }

        public virtual void sendData(Channel channel, byte[] buffer, int pos, int len)
        {
            while (len > 0)
            {
                int thislen = 0;
                byte[] msg;

                lock (channel)
                {
                    while (true)
                    {
                        if (channel.state == Channel.STATE_CLOSED)
                            throw new System.IO.IOException("SSH channel is closed. (" + channel.ReasonClosed + ")");

                        if (channel.state != Channel.STATE_OPEN)
                            throw new System.IO.IOException("SSH channel in strange state. (" + channel.state + ")");

                        if (channel.remoteWindow != 0)
                            break;

                        try
                        {
                            System.Threading.Monitor.Wait(channel);
                        }
                        catch (System.Threading.ThreadInterruptedException ignore)
                        {
                            Thread.CurrentThread.Interrupt();
                        }
                    }

                    /* len > 0, no sign extension can happen when comparing */

                    thislen = (channel.remoteWindow >= len) ? len : (int)channel.remoteWindow;

                    int estimatedMaxDataLen = channel.remoteMaxPacketSize - (tm.PacketOverheadEstimate + 9);

                    /* The worst case scenario =) a true bottleneck */

                    if (estimatedMaxDataLen <= 0)
                    {
                        estimatedMaxDataLen = 1;
                    }

                    if (thislen > estimatedMaxDataLen)
                        thislen = estimatedMaxDataLen;

                    channel.remoteWindow -= thislen;

                    msg = new byte[1 + 8 + thislen];

                    msg[0] = (byte)(Packets.SSH_MSG_CHANNEL_DATA);
                    msg[1] = (byte)(channel.remoteID >> 24);
                    msg[2] = (byte)(channel.remoteID >> 16);
                    msg[3] = (byte)(channel.remoteID >> 8);
                    msg[4] = (byte)(channel.remoteID);
                    msg[5] = (byte)(thislen >> 24);
                    msg[6] = (byte)(thislen >> 16);
                    msg[7] = (byte)(thislen >> 8);
                    msg[8] = (byte)(thislen);

                    Array.Copy(buffer, pos, msg, 9, thislen);
                }

                lock (channel.channelSendLock)
                {
                    if (channel.closeMessageSent == true)
                        throw new System.IO.IOException("SSH channel is closed. (" + channel.ReasonClosed + ")");

                    tm.sendMessage(msg);
                }

                pos += thislen;
                len -= thislen;
            }
        }

        public virtual int requestGlobalForward(System.String bindAddress, int bindPort, System.String targetAddress, int targetPort)
        {
            RemoteForwardingData rfd = new RemoteForwardingData();

            rfd.bindAddress = bindAddress;
            rfd.bindPort = bindPort;
            rfd.targetAddress = targetAddress;
            rfd.targetPort = targetPort;

            //lock (remoteForwardings.SyncRoot)
            lock (remoteForwardings)
            {
                System.Int32 key = (System.Int32)bindPort;

                //UPGRADE_TODO: Method 'java.util.HashMap.get' was converted to 'System.Collections.Hashtable.Item' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilHashMapget_javalangObject'"
                if (remoteForwardings[key] != null)
                {
                    throw new System.IO.IOException("There is already a forwarding for remote port " + bindPort);
                }

                remoteForwardings[key] = rfd;
            }

            //lock (channels.SyncRoot)
            lock (channels)
            {
                globalSuccessCounter = globalFailedCounter = 0;
            }

            PacketGlobalForwardRequest pgf = new PacketGlobalForwardRequest(true, bindAddress, bindPort);
            tm.sendMessage(pgf.Payload);

            if (log.Enabled)
                log.log(50, "Requesting a remote forwarding ('" + bindAddress + "', " + bindPort + ")");

            try
            {
                waitForGlobalSuccessOrFailure();
            }
            catch (System.IO.IOException e)
            {
                //lock (remoteForwardings.SyncRoot)
                lock (remoteForwardings)
                {
                    remoteForwardings.Remove(rfd);
                }
                throw e;
            }

            return bindPort;
        }

        public virtual void requestCancelGlobalForward(int bindPort)
        {
            RemoteForwardingData rfd = null;

            //lock (remoteForwardings.SyncRoot)
            lock (remoteForwardings)
            {
                //UPGRADE_TODO: Method 'java.util.HashMap.get' was converted to 'System.Collections.Hashtable.Item' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilHashMapget_javalangObject'"
                rfd = (RemoteForwardingData)remoteForwardings[(System.Int32)bindPort];

                if (rfd == null)
                    throw new System.IO.IOException("Sorry, there is no known remote forwarding for remote port " + bindPort);
            }

            //lock (channels.SyncRoot)
            lock (channels)
            {
                globalSuccessCounter = globalFailedCounter = 0;
            }

            PacketGlobalCancelForwardRequest pgcf = new PacketGlobalCancelForwardRequest(true, rfd.bindAddress, rfd.bindPort);
            tm.sendMessage(pgcf.Payload);

            if (log.Enabled)
                log.log(50, "Requesting cancelation of remote forward ('" + rfd.bindAddress + "', " + rfd.bindPort + ")");

            waitForGlobalSuccessOrFailure();

            /* Only now we are sure that no more forwarded connections will arrive */

            //lock (remoteForwardings.SyncRoot)
            lock (remoteForwardings)
            {
                remoteForwardings.Remove(rfd);
            }
        }

        public virtual void registerThread(IChannelWorkerThread thr)
        {
            //lock (listenerThreads.SyncRoot)
            lock (listenerThreads)
            {
                if (listenerThreadsAllowed == false)
                    throw new System.IO.IOException("Too late, this connection is closed.");
                listenerThreads.Add(thr);
            }
        }

        public virtual Channel openDirectTCPIPChannel(System.String host_to_connect, int port_to_connect, System.String originator_IP_address, int originator_port)
        {
            Channel channel = new Channel(this);

            lock (channel)
            {
                channel.localID = addChannel(channel);
                // end of synchronized block forces writing out to main memory
            }

            PacketOpenDirectTCPIPChannel dtc = new PacketOpenDirectTCPIPChannel(channel.localID, channel.localWindow, channel.localMaxPacketSize, host_to_connect, port_to_connect, originator_IP_address, originator_port);

            tm.sendMessage(dtc.Payload);

            waitUntilChannelOpen(channel);

            return channel;
        }

        public virtual Channel openSessionChannel()
        {
            Channel channel = new Channel(this);

            lock (channel)
            {
                channel.localID = addChannel(channel);
                // end of synchronized block forces the writing out to main memory
            }

            if (log.Enabled)
                log.log(50, "Sending SSH_MSG_CHANNEL_OPEN (Channel " + channel.localID + ")");

            PacketOpenSessionChannel smo = new PacketOpenSessionChannel(channel.localID, channel.localWindow, channel.localMaxPacketSize);
            tm.sendMessage(smo.Payload);

            waitUntilChannelOpen(channel);

            return channel;
        }

        public virtual void requestPTY(Channel c, System.String term, int term_width_characters, int term_height_characters, int term_width_pixels, int term_height_pixels, byte[] terminal_modes)
        {
            PacketSessionPtyRequest spr;

            lock (c)
            {
                if (c.state != Channel.STATE_OPEN)
                    throw new System.IO.IOException("Cannot request PTY on this channel (" + c.ReasonClosed + ")");

                spr = new PacketSessionPtyRequest(c.remoteID, true, term, term_width_characters, term_height_characters, term_width_pixels, term_height_pixels, terminal_modes);

                c.successCounter = c.failedCounter = 0;
            }

            lock (c.channelSendLock)
            {
                if (c.closeMessageSent)
                    throw new System.IO.IOException("Cannot request PTY on this channel (" + c.ReasonClosed + ")");
                tm.sendMessage(spr.Payload);
            }

            try
            {
                waitForChannelSuccessOrFailure(c);
            }
            catch (System.IO.IOException e)
            {
                throw (System.IO.IOException)new System.IO.IOException("PTY request failed", e);
            }
        }

        public virtual void requestX11(Channel c, bool singleConnection, System.String x11AuthenticationProtocol, System.String x11AuthenticationCookie, int x11ScreenNumber)
        {
            PacketSessionX11Request psr;

            lock (c)
            {
                if (c.state != Channel.STATE_OPEN)
                    throw new System.IO.IOException("Cannot request X11 on this channel (" + c.ReasonClosed + ")");

                psr = new PacketSessionX11Request(c.remoteID, true, singleConnection, x11AuthenticationProtocol, x11AuthenticationCookie, x11ScreenNumber);

                c.successCounter = c.failedCounter = 0;
            }

            lock (c.channelSendLock)
            {
                if (c.closeMessageSent)
                    throw new System.IO.IOException("Cannot request X11 on this channel (" + c.ReasonClosed + ")");
                tm.sendMessage(psr.Payload);
            }

            if (log.Enabled)
                log.log(50, "Requesting X11 forwarding (Channel " + c.localID + "/" + c.remoteID + ")");

            try
            {
                waitForChannelSuccessOrFailure(c);
            }
            catch (System.IO.IOException e)
            {
                throw (System.IO.IOException)new System.IO.IOException("The X11 request failed.", e);
            }
        }

        public virtual void requestSubSystem(Channel c, System.String subSystemName)
        {
            PacketSessionSubsystemRequest ssr;

            lock (c)
            {
                if (c.state != Channel.STATE_OPEN)
                    throw new System.IO.IOException("Cannot request subsystem on this channel (" + c.ReasonClosed + ")");

                ssr = new PacketSessionSubsystemRequest(c.remoteID, true, subSystemName);

                c.successCounter = c.failedCounter = 0;
            }

            lock (c.channelSendLock)
            {
                if (c.closeMessageSent)
                    throw new System.IO.IOException("Cannot request subsystem on this channel (" + c.ReasonClosed + ")");
                tm.sendMessage(ssr.Payload);
            }

            try
            {
                waitForChannelSuccessOrFailure(c);
            }
            catch (System.IO.IOException e)
            {
                throw (System.IO.IOException)new System.IO.IOException("The subsystem request failed.", e);
            }
        }

        public virtual void requestExecCommand(Channel c, System.String cmd)
        {
            PacketSessionExecCommand sm;

            lock (c)
            {
                if (c.state != Channel.STATE_OPEN)
                    throw new System.IO.IOException("Cannot execute command on this channel (" + c.ReasonClosed + ")");

                sm = new PacketSessionExecCommand(c.remoteID, true, cmd);

                c.successCounter = c.failedCounter = 0;
            }

            lock (c.channelSendLock)
            {
                if (c.closeMessageSent)
                    throw new System.IO.IOException("Cannot execute command on this channel (" + c.ReasonClosed + ")");
                tm.sendMessage(sm.Payload);
            }

            if (log.Enabled)
                log.log(50, "Executing command (channel " + c.localID + ", '" + cmd + "')");

            try
            {
                waitForChannelSuccessOrFailure(c);
            }
            catch (System.IO.IOException e)
            {
                throw (System.IO.IOException)new System.IO.IOException("The execute request failed.", e);
            }
        }

        public virtual void requestShell(Channel c)
        {
            PacketSessionStartShell sm;

            lock (c)
            {
                if (c.state != Channel.STATE_OPEN)
                    throw new System.IO.IOException("Cannot start shell on this channel (" + c.ReasonClosed + ")");

                sm = new PacketSessionStartShell(c.remoteID, true);

                c.successCounter = c.failedCounter = 0;
            }

            lock (c.channelSendLock)
            {
                if (c.closeMessageSent)
                    throw new System.IO.IOException("Cannot start shell on this channel (" + c.ReasonClosed + ")");
                tm.sendMessage(sm.Payload);
            }

            try
            {
                waitForChannelSuccessOrFailure(c);
            }
            catch (System.IO.IOException e)
            {
                throw (System.IO.IOException)new System.IO.IOException("The shell request failed.", e);
            }
        }

        public virtual void msgChannelExtendedData(byte[] msg, int msglen)
        {
            if (msglen <= 13)
                throw new System.IO.IOException("SSH_MSG_CHANNEL_EXTENDED_DATA message has wrong size (" + msglen + ")");

            int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff);
            int dataType = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff);
            int len = ((msg[9] & 0xff) << 24) | ((msg[10] & 0xff) << 16) | ((msg[11] & 0xff) << 8) | (msg[12] & 0xff);

            Channel channel = getChannel(id);

            if (channel == null)
                throw new System.IO.IOException("Unexpected SSH_MSG_CHANNEL_EXTENDED_DATA message for non-existent channel " + id);

            if (dataType != Packets.SSH_EXTENDED_DATA_STDERR)
                throw new System.IO.IOException("SSH_MSG_CHANNEL_EXTENDED_DATA message has unknown type (" + dataType + ")");

            if (len != (msglen - 13))
                throw new System.IO.IOException("SSH_MSG_CHANNEL_EXTENDED_DATA message has wrong len (calculated " + (msglen - 13) + ", got " + len + ")");

            if (log.Enabled)
                log.log(80, "Got SSH_MSG_CHANNEL_EXTENDED_DATA (channel " + id + ", " + len + ")");

            lock (channel)
            {
                if (channel.state == Channel.STATE_CLOSED)
                    return; // ignore

                if (channel.state != Channel.STATE_OPEN)
                    throw new System.IO.IOException("Got SSH_MSG_CHANNEL_EXTENDED_DATA, but channel is not in correct state (" + channel.state + ")");

                if (channel.localWindow < len)
                    throw new System.IO.IOException("Remote sent too much data, does not fit into window.");

                channel.localWindow -= len;

                Array.Copy(msg, 13, channel.stderrBuffer, channel.stderrWritepos, len);
                channel.stderrWritepos += len;

                System.Threading.Monitor.PulseAll(channel);
            }
        }

        /// <summary> Wait until for a condition.
        /// 
        /// </summary>
        /// <param name="c">Channel
        /// </param>
        /// <param name="timeout">in ms, 0 means no timeout.
        /// </param>
        /// <param name="condition_mask">minimum event mask
        /// </param>
        /// <returns> all current events
        /// 
        /// </returns>
        public virtual int waitForCondition(Channel channel, long timeout, int condition_mask)
        {
            long end_time = 0;
            bool end_time_set = false;

            lock (channel)
            {
                while (true)
                {
                    int current_cond = 0;

                    int stdoutAvail = channel.stdoutWritepos - channel.stdoutReadpos;
                    int stderrAvail = channel.stderrWritepos - channel.stderrReadpos;

                    if (stdoutAvail > 0)
                        current_cond = current_cond | ch.ethz.ssh2.ChannelCondition_Fields.STDOUT_DATA;

                    if (stderrAvail > 0)
                        current_cond = current_cond | ch.ethz.ssh2.ChannelCondition_Fields.STDERR_DATA;

                    if (channel.EOF)
                        current_cond = current_cond | ch.ethz.ssh2.ChannelCondition_Fields.EOF;

                    if (channel.ExitStatus != null)
                        current_cond = current_cond | ch.ethz.ssh2.ChannelCondition_Fields.EXIT_STATUS;

                    if (channel.ExitSignal != null)
                        current_cond = current_cond | ch.ethz.ssh2.ChannelCondition_Fields.EXIT_SIGNAL;

                    if (channel.state == Channel.STATE_CLOSED)
                        return current_cond | ch.ethz.ssh2.ChannelCondition_Fields.CLOSED | ch.ethz.ssh2.ChannelCondition_Fields.EOF;

                    if ((current_cond & condition_mask) != 0)
                        return current_cond;

                    if (timeout > 0)
                    {
                        if (!end_time_set)
                        {
                            end_time = (System.DateTime.Now.Ticks - 621355968000000000) / 10000 + timeout;
                            end_time_set = true;
                        }
                        else
                        {
                            timeout = end_time - (System.DateTime.Now.Ticks - 621355968000000000) / 10000;

                            if (timeout <= 0)
                                return current_cond | ch.ethz.ssh2.ChannelCondition_Fields.TIMEOUT;
                        }
                    }

                    try
                    {
                        if (timeout > 0)
                            System.Threading.Monitor.Wait(channel, TimeSpan.FromMilliseconds(timeout));
                        else
                            System.Threading.Monitor.Wait(channel);
                    }
                    catch (System.Threading.ThreadInterruptedException e)
                    {
                        Thread.CurrentThread.Interrupt();
                    }
                }
            }
        }

        public virtual int getAvailable(Channel c, bool extended)
        {
            lock (c)
            {
                int avail;

                if (extended)
                    avail = c.stderrWritepos - c.stderrReadpos;
                else
                    avail = c.stdoutWritepos - c.stdoutReadpos;

                return ((avail > 0) ? avail : (c.EOF ? -1 : 0));
            }
        }

        public virtual int getChannelData(Channel channel, bool extended, byte[] target, int off, int len)
        {
            int copylen = 0;
            int increment = 0;
            int remoteID = 0;
            int localID = 0;

            lock (channel)
            {
                int stdoutAvail = 0;
                int stderrAvail = 0;

                while (true)
                {
                    /*
                    * Data available? We have to return remaining data even if the
                    * channel is already closed.
                    */

                    stdoutAvail = channel.stdoutWritepos - channel.stdoutReadpos;
                    stderrAvail = channel.stderrWritepos - channel.stderrReadpos;

                    if ((!extended) && (stdoutAvail != 0))
                        break;

                    if ((extended) && (stderrAvail != 0))
                        break;

                    /* Do not wait if more data will never arrive (EOF or CLOSED) */

                    if ((channel.EOF) || (channel.state != Channel.STATE_OPEN))
                        return -1;

                    try
                    {
                        System.Threading.Monitor.Wait(channel);
                    }
                    catch (System.Threading.ThreadInterruptedException ignore)
                    {
                        Thread.CurrentThread.Interrupt();
                    }
                }

                /* OK, there is some data. Return it. */

                if (!extended)
                {
                    copylen = (stdoutAvail > len) ? len : stdoutAvail;
                    Array.Copy(channel.stdoutBuffer, channel.stdoutReadpos, target, off, copylen);
                    channel.stdoutReadpos += copylen;

                    if (channel.stdoutReadpos != channel.stdoutWritepos)
                        Array.Copy(channel.stdoutBuffer, channel.stdoutReadpos, channel.stdoutBuffer, 0, channel.stdoutWritepos - channel.stdoutReadpos);

                    channel.stdoutWritepos -= channel.stdoutReadpos;
                    channel.stdoutReadpos = 0;
                }
                else
                {
                    copylen = (stderrAvail > len) ? len : stderrAvail;
                    Array.Copy(channel.stderrBuffer, channel.stderrReadpos, target, off, copylen);
                    channel.stderrReadpos += copylen;

                    if (channel.stderrReadpos != channel.stderrWritepos)
                        Array.Copy(channel.stderrBuffer, channel.stderrReadpos, channel.stderrBuffer, 0, channel.stderrWritepos - channel.stderrReadpos);

                    channel.stderrWritepos -= channel.stderrReadpos;
                    channel.stderrReadpos = 0;
                }

                if (channel.state != Channel.STATE_OPEN)
                    return copylen;

                if (channel.localWindow < ((Channel.CHANNEL_BUFFER_SIZE + 1) / 2))
                {
                    int minFreeSpace = System.Math.Min(Channel.CHANNEL_BUFFER_SIZE - channel.stdoutWritepos, Channel.CHANNEL_BUFFER_SIZE - channel.stderrWritepos);

                    increment = minFreeSpace - channel.localWindow;
                    channel.localWindow = minFreeSpace;
                }

                remoteID = channel.remoteID; /* read while holding the lock */
                localID = channel.localID; /* read while holding the lock */
            }

            /*
            * If a consumer reads stdout and stdin in parallel, we may end up with
            * sending two msgWindowAdjust messages. Luckily, it
            * does not matter in which order they arrive at the server.
            */

            if (increment > 0)
            {
                if (log.Enabled)
                    log.log(80, "Sending SSH_MSG_CHANNEL_WINDOW_ADJUST (channel " + localID + ", " + increment + ")");

                lock (channel.channelSendLock)
                {
                    byte[] msg = channel.msgWindowAdjust;

                    msg[0] = (byte)(Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST);
                    msg[1] = (byte)(remoteID >> 24);
                    msg[2] = (byte)(remoteID >> 16);
                    msg[3] = (byte)(remoteID >> 8);
                    msg[4] = (byte)(remoteID);
                    msg[5] = (byte)(increment >> 24);
                    msg[6] = (byte)(increment >> 16);
                    msg[7] = (byte)(increment >> 8);
                    msg[8] = (byte)(increment);

                    if (channel.closeMessageSent == false)
                        tm.sendMessage(msg);
                }
            }

            return copylen;
        }

        public virtual void msgChannelData(byte[] msg, int msglen)
        {
            if (msglen <= 9)
                throw new System.IO.IOException("SSH_MSG_CHANNEL_DATA message has wrong size (" + msglen + ")");

            int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff);
            int len = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff);

            Channel channel = getChannel(id);

            if (channel == null)
                throw new System.IO.IOException("Unexpected SSH_MSG_CHANNEL_DATA message for non-existent channel " + id);

            if (len != (msglen - 9))
                throw new System.IO.IOException("SSH_MSG_CHANNEL_DATA message has wrong len (calculated " + (msglen - 9) + ", got " + len + ")");

            if (log.Enabled)
                log.log(80, "Got SSH_MSG_CHANNEL_DATA (channel " + id + ", " + len + ")");

            lock (channel)
            {
                if (channel.state == Channel.STATE_CLOSED)
                    return; // ignore

                if (channel.state != Channel.STATE_OPEN)
                    throw new System.IO.IOException("Got SSH_MSG_CHANNEL_DATA, but channel is not in correct state (" + channel.state + ")");

                if (channel.localWindow < len)
                    throw new System.IO.IOException("Remote sent too much data, does not fit into window.");

                channel.localWindow -= len;

                Array.Copy(msg, 9, channel.stdoutBuffer, channel.stdoutWritepos, len);
                channel.stdoutWritepos += len;

                System.Threading.Monitor.PulseAll(channel);
            }
        }

        public virtual void msgChannelWindowAdjust(byte[] msg, int msglen)
        {
            if (msglen != 9)
                throw new System.IO.IOException("SSH_MSG_CHANNEL_WINDOW_ADJUST message has wrong size (" + msglen + ")");

            int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff);
            int windowChange = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff);

            Channel channel = getChannel(id);

            if (channel == null)
                throw new System.IO.IOException("Unexpected SSH_MSG_CHANNEL_WINDOW_ADJUST message for non-existent channel " + id);

            lock (channel)
            {
                //UPGRADE_NOTE: Final was removed from the declaration of 'huge '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
                long huge = 0xFFFFffffL; /* 2^32 - 1 */

                channel.remoteWindow += (windowChange & huge); /* avoid sign extension */

                /* TODO - is this a good heuristic? */

                if ((channel.remoteWindow > huge))
                    channel.remoteWindow = huge;

                System.Threading.Monitor.PulseAll(channel);
            }

            if (log.Enabled)
                log.log(80, "Got SSH_MSG_CHANNEL_WINDOW_ADJUST (channel " + id + ", " + windowChange + ")");
        }

        public virtual void msgChannelOpen(byte[] msg, int msglen)
        {
            TypesReader tr = new TypesReader(msg, 0, msglen);

            tr.readByte(); // skip packet type
            System.String channelType = tr.readString();
            int remoteID = tr.readUINT32(); /* sender channel */
            int remoteWindow = tr.readUINT32(); /* initial window size */
            int remoteMaxPacketSize = tr.readUINT32(); /* maximum packet size */

            if ("x11".Equals(channelType))
            {
                //lock (x11_magic_cookies.SyncRoot)
                lock (x11_magic_cookies)
                {
                    /* If we did not request X11 forwarding, then simply ignore this bogus request. */

                    if (x11_magic_cookies.Count == 0)
                    {
                        PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID, Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, "X11 forwarding not activated", "");

                        tm.sendAsynchronousMessage(pcof.Payload);

                        if (log.Enabled)
                            log.log(20, "Unexpected X11 request, denying it!");

                        return;
                    }
                }

                System.String remoteOriginatorAddress = tr.readString();
                int remoteOriginatorPort = tr.readUINT32();

                Channel channel = new Channel(this);

                lock (channel)
                {
                    channel.remoteID = remoteID;
                    channel.remoteWindow = remoteWindow & unchecked((int)0xFFFFffffL); /* properly convert UINT32 to long */
                    channel.remoteMaxPacketSize = remoteMaxPacketSize;
                    channel.localID = addChannel(channel);
                }

                /*
                * The open confirmation message will be sent from another thread
                */

                Thread rxat = new Thread(new ThreadStart(new RemoteX11AcceptThread(channel, remoteOriginatorAddress, remoteOriginatorPort).Run));
                rxat.IsBackground = true;
                rxat.Start();

                return;
            }

            if ("forwarded-tcpip".Equals(channelType))
            {
                System.String remoteConnectedAddress = tr.readString(); /* address that was connected */
                int remoteConnectedPort = tr.readUINT32(); /* port that was connected */
                System.String remoteOriginatorAddress = tr.readString(); /* originator IP address */
                int remoteOriginatorPort = tr.readUINT32(); /* originator port */

                RemoteForwardingData rfd = null;

                //lock (remoteForwardings.SyncRoot)
                lock (remoteForwardings)
                {
                    //UPGRADE_TODO: Method 'java.util.HashMap.get' was converted to 'System.Collections.Hashtable.Item' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilHashMapget_javalangObject'"
                    rfd = (RemoteForwardingData)remoteForwardings[(System.Int32)remoteConnectedPort];
                }

                if (rfd == null)
                {
                    PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID, Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, "No thanks, unknown port in forwarded-tcpip request", "");

                    /* Always try to be polite. */

                    tm.sendAsynchronousMessage(pcof.Payload);

                    if (log.Enabled)
                        log.log(20, "Unexpected forwarded-tcpip request, denying it!");

                    return;
                }

                Channel channel = new Channel(this);

                lock (channel)
                {
                    channel.remoteID = remoteID;
                    channel.remoteWindow = remoteWindow & unchecked((int)0xFFFFffffL); /* convert UINT32 to long */
                    channel.remoteMaxPacketSize = remoteMaxPacketSize;
                    channel.localID = addChannel(channel);
                }

                /*
                * The open confirmation message will be sent from another thread.
                */

                Thread rat = new Thread(new ThreadStart(new RemoteAcceptThread(channel, remoteConnectedAddress, remoteConnectedPort, remoteOriginatorAddress, remoteOriginatorPort, rfd.targetAddress, rfd.targetPort).Run));

                rat.IsBackground = true;
                rat.Start();

                return;
            }

            /* Tell the server that we have no idea what it is talking about */

            PacketChannelOpenFailure pcof2 = new PacketChannelOpenFailure(remoteID, Packets.SSH_OPEN_UNKNOWN_CHANNEL_TYPE, "Unknown channel type", "");

            tm.sendAsynchronousMessage(pcof2.Payload);

            if (log.Enabled)
                log.log(20, "The peer tried to open an unsupported channel type (" + channelType + ")");
        }

        public virtual void msgChannelRequest(byte[] msg, int msglen)
        {
            TypesReader tr = new TypesReader(msg, 0, msglen);

            tr.readByte(); // skip packet type
            int id = tr.readUINT32();

            Channel channel = getChannel(id);

            if (channel == null)
                throw new System.IO.IOException("Unexpected SSH_MSG_CHANNEL_REQUEST message for non-existent channel " + id);

            System.String type = tr.readString("US-ASCII");
            bool wantReply = tr.readBoolean();

            if (log.Enabled)
                log.log(80, "Got SSH_MSG_CHANNEL_REQUEST (channel " + id + ", '" + type + "')");

            if (type.Equals("exit-status"))
            {
                if (wantReply != false)
                    throw new System.IO.IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message, 'want reply' is true");

                int exit_status = tr.readUINT32();

                if (tr.remain() != 0)
                    throw new System.IO.IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message");

                lock (channel)
                {
                    channel.exit_status = (System.Int32)exit_status;
                    System.Threading.Monitor.PulseAll(channel);
                }

                if (log.Enabled)
                    log.log(50, "Got EXIT STATUS (channel " + id + ", status " + exit_status + ")");

                return;
            }

            if (type.Equals("exit-signal"))
            {
                if (wantReply != false)
                    throw new System.IO.IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message, 'want reply' is true");

                System.String signame = tr.readString("US-ASCII");
                tr.readBoolean();
                tr.readString();
                tr.readString();

                if (tr.remain() != 0)
                    throw new System.IO.IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message");

                lock (channel)
                {
                    channel.exit_signal = signame;
                    System.Threading.Monitor.PulseAll(channel);
                }

                if (log.Enabled)
                    log.log(50, "Got EXIT SIGNAL (channel " + id + ", signal " + signame + ")");

                return;
            }

            /* We simply ignore unknown channel requests, however, if the server wants a reply,
            * then we signal that we have no idea what it is about.
            */

            if (wantReply)
            {
                byte[] reply = new byte[5];

                reply[0] = (byte)(Packets.SSH_MSG_CHANNEL_FAILURE);
                reply[1] = (byte)(channel.remoteID >> 24);
                reply[2] = (byte)(channel.remoteID >> 16);
                reply[3] = (byte)(channel.remoteID >> 8);
                reply[4] = (byte)(channel.remoteID);

                tm.sendAsynchronousMessage(reply);
            }

            if (log.Enabled)
                log.log(50, "Channel request '" + type + "' is not known, ignoring it");
        }

        public virtual void msgChannelEOF(byte[] msg, int msglen)
        {
            if (msglen != 5)
                throw new System.IO.IOException("SSH_MSG_CHANNEL_EOF message has wrong size (" + msglen + ")");

            int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff);

            Channel channel = getChannel(id);

            if (channel == null)
                throw new System.IO.IOException("Unexpected SSH_MSG_CHANNEL_EOF message for non-existent channel " + id);

            lock (channel)
            {
                channel.EOF = true;
                System.Threading.Monitor.PulseAll(channel);
            }

            if (log.Enabled)
                log.log(50, "Got SSH_MSG_CHANNEL_EOF (channel " + id + ")");
        }

        public virtual void msgChannelClose(byte[] msg, int msglen)
        {
            if (msglen != 5)
                throw new System.IO.IOException("SSH_MSG_CHANNEL_CLOSE message has wrong size (" + msglen + ")");

            int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff);

            Channel channel = getChannel(id);

            if (channel == null)
                throw new System.IO.IOException("Unexpected SSH_MSG_CHANNEL_CLOSE message for non-existent channel " + id);

            lock (channel)
            {
                channel.EOF = true;
                channel.state = Channel.STATE_CLOSED;
                channel.ReasonClosed = "Close requested by remote";
                channel.closeMessageRecv = true;

                removeChannel(channel.localID);

                System.Threading.Monitor.PulseAll(channel);
            }

            if (log.Enabled)
                log.log(50, "Got SSH_MSG_CHANNEL_CLOSE (channel " + id + ")");
        }

        public virtual void msgChannelSuccess(byte[] msg, int msglen)
        {
            if (msglen != 5)
                throw new System.IO.IOException("SSH_MSG_CHANNEL_SUCCESS message has wrong size (" + msglen + ")");

            int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff);

            Channel channel = getChannel(id);

            if (channel == null)
                throw new System.IO.IOException("Unexpected SSH_MSG_CHANNEL_SUCCESS message for non-existent channel " + id);

            lock (channel)
            {
                channel.successCounter++;
                System.Threading.Monitor.PulseAll(channel);
            }

            if (log.Enabled)
                log.log(80, "Got SSH_MSG_CHANNEL_SUCCESS (channel " + id + ")");
        }

        public virtual void msgChannelFailure(byte[] msg, int msglen)
        {
            if (msglen != 5)
                throw new System.IO.IOException("SSH_MSG_CHANNEL_FAILURE message has wrong size (" + msglen + ")");

            int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff);

            Channel channel = getChannel(id);

            if (channel == null)
                throw new System.IO.IOException("Unexpected SSH_MSG_CHANNEL_FAILURE message for non-existent channel " + id);

            lock (channel)
            {
                channel.failedCounter++;
                System.Threading.Monitor.PulseAll(channel);
            }

            if (log.Enabled)
                log.log(50, "Got SSH_MSG_CHANNEL_FAILURE (channel " + id + ")");
        }

        public virtual void msgChannelOpenConfirmation(byte[] msg, int msglen)
        {
            PacketChannelOpenConfirmation sm = new PacketChannelOpenConfirmation(msg, 0, msglen);

            Channel channel = getChannel(sm.recipientChannelID);

            if (channel == null)
                throw new System.IO.IOException("Unexpected SSH_MSG_CHANNEL_OPEN_CONFIRMATION message for non-existent channel " + sm.recipientChannelID);

            lock (channel)
            {
                if (channel.state != Channel.STATE_OPENING)
                    throw new System.IO.IOException("Unexpected SSH_MSG_CHANNEL_OPEN_CONFIRMATION message for channel " + sm.recipientChannelID);

                channel.remoteID = sm.senderChannelID;
                channel.remoteWindow = sm.initialWindowSize & unchecked((int)0xFFFFffffL); /* convert UINT32 to long */
                channel.remoteMaxPacketSize = sm.maxPacketSize;
                channel.state = Channel.STATE_OPEN;
                System.Threading.Monitor.PulseAll(channel);
            }

            if (log.Enabled)
                log.log(50, "Got SSH_MSG_CHANNEL_OPEN_CONFIRMATION (channel " + sm.recipientChannelID + " / remote: " + sm.senderChannelID + ")");
        }

        public virtual void msgChannelOpenFailure(byte[] msg, int msglen)
        {
            if (msglen < 5)
                throw new System.IO.IOException("SSH_MSG_CHANNEL_OPEN_FAILURE message has wrong size (" + msglen + ")");

            TypesReader tr = new TypesReader(msg, 0, msglen);

            tr.readByte(); // skip packet type
            int id = tr.readUINT32(); /* sender channel */

            Channel channel = getChannel(id);

            if (channel == null)
                throw new System.IO.IOException("Unexpected SSH_MSG_CHANNEL_OPEN_FAILURE message for non-existent channel " + id);

            int reasonCode = tr.readUINT32();
            System.String description = tr.readString("UTF-8");

            System.String reasonCodeSymbolicName = null;

            switch (reasonCode)
            {

                case 1:
                    reasonCodeSymbolicName = "SSH_OPEN_ADMINISTRATIVELY_PROHIBITED";
                    break;

                case 2:
                    reasonCodeSymbolicName = "SSH_OPEN_CONNECT_FAILED";
                    break;

                case 3:
                    reasonCodeSymbolicName = "SSH_OPEN_UNKNOWN_CHANNEL_TYPE";
                    break;

                case 4:
                    reasonCodeSymbolicName = "SSH_OPEN_RESOURCE_SHORTAGE";
                    break;

                default:
                    reasonCodeSymbolicName = "UNKNOWN REASON CODE (" + reasonCode + ")";
                    break;

            }

            System.Text.StringBuilder descriptionBuffer = new System.Text.StringBuilder();
            descriptionBuffer.Append(description);

            for (int i = 0; i < descriptionBuffer.Length; i++)
            {
                char cc = descriptionBuffer[i];

                if ((cc >= 32) && (cc <= 126))
                    continue;
                descriptionBuffer[i] = '\uFFFD';
            }

            lock (channel)
            {
                channel.EOF = true;
                channel.state = Channel.STATE_CLOSED;
                channel.ReasonClosed = "The server refused to open the channel (" + reasonCodeSymbolicName + ", '" + descriptionBuffer.ToString() + "')";
                System.Threading.Monitor.PulseAll(channel);
            }

            if (log.Enabled)
                log.log(50, "Got SSH_MSG_CHANNEL_OPEN_FAILURE (channel " + id + ")");
        }

        public virtual void msgGlobalRequest(byte[] msg, int msglen)
        {
            /* Currently we do not support any kind of global request */

            TypesReader tr = new TypesReader(msg, 0, msglen);

            tr.readByte(); // skip packet type
            System.String requestName = tr.readString();
            bool wantReply = tr.readBoolean();

            if (wantReply)
            {
                byte[] reply_failure = new byte[1];
                reply_failure[0] = (byte)(Packets.SSH_MSG_REQUEST_FAILURE);

                tm.sendAsynchronousMessage(reply_failure);
            }

            /* We do not clean up the requestName String - that is OK for debug */

            if (log.Enabled)
                log.log(80, "Got SSH_MSG_GLOBAL_REQUEST (" + requestName + ")");
        }

        public virtual void msgGlobalSuccess()
        {
            //lock (channels.SyncRoot)
            lock (channels)
            {
                globalSuccessCounter++;
                System.Threading.Monitor.PulseAll(channels);
            }

            if (log.Enabled)
                log.log(80, "Got SSH_MSG_REQUEST_SUCCESS");
        }

        public virtual void msgGlobalFailure()
        {
            //lock (channels.SyncRoot)
            lock (channels)
            {
                globalFailedCounter++;
                System.Threading.Monitor.PulseAll(channels);
            }

            if (log.Enabled)
                log.log(80, "Got SSH_MSG_REQUEST_FAILURE");
        }

        public virtual void handleMessage(byte[] msg, int msglen)
        {
            if (msg == null)
            {
                if (log.Enabled)
                    log.log(50, "HandleMessage: got shutdown");

                //lock (listenerThreads.SyncRoot)
                lock (listenerThreads)
                {
                    for (int i = 0; i < listenerThreads.Count; i++)
                    {
                        IChannelWorkerThread lat = (IChannelWorkerThread)listenerThreads[i];
                        lat.stopWorking();
                    }
                    listenerThreadsAllowed = false;
                }

                //lock (channels.SyncRoot)
                lock (channels)
                {
                    shutdown = true;

                    for (int i = 0; i < channels.Count; i++)
                    {
                        Channel channel = (Channel)channels[i];
                        lock (channel)
                        {
                            channel.EOF = true;
                            channel.state = Channel.STATE_CLOSED;
                            channel.ReasonClosed = "The connection is being shutdown";
                            channel.closeMessageRecv = true; /*
							* You never know, perhaps
							* we are waiting for a
							* pending close message
							* from the server...
							*/
                            System.Threading.Monitor.PulseAll(channel);
                        }
                    }
                    /* Works with J2ME */
                    channels.Clear();
                    channels.Capacity = 0;
                    channels.TrimToSize();
                    System.Threading.Monitor.PulseAll(channels); /* Notify global response waiters */
                    return;
                }
            }

            switch (msg[0])
            {

                case (byte)(Packets.SSH_MSG_CHANNEL_OPEN_CONFIRMATION):
                    msgChannelOpenConfirmation(msg, msglen);
                    break;

                case (byte)(Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST):
                    msgChannelWindowAdjust(msg, msglen);
                    break;

                case (byte)(Packets.SSH_MSG_CHANNEL_DATA):
                    msgChannelData(msg, msglen);
                    break;

                case (byte)(Packets.SSH_MSG_CHANNEL_EXTENDED_DATA):
                    msgChannelExtendedData(msg, msglen);
                    break;

                case (byte)(Packets.SSH_MSG_CHANNEL_REQUEST):
                    msgChannelRequest(msg, msglen);
                    break;

                case (byte)(Packets.SSH_MSG_CHANNEL_EOF):
                    msgChannelEOF(msg, msglen);
                    break;

                case (byte)(Packets.SSH_MSG_CHANNEL_OPEN):
                    msgChannelOpen(msg, msglen);
                    break;

                case (byte)(Packets.SSH_MSG_CHANNEL_CLOSE):
                    msgChannelClose(msg, msglen);
                    break;

                case (byte)(Packets.SSH_MSG_CHANNEL_SUCCESS):
                    msgChannelSuccess(msg, msglen);
                    break;

                case (byte)(Packets.SSH_MSG_CHANNEL_FAILURE):
                    msgChannelFailure(msg, msglen);
                    break;

                case (byte)(Packets.SSH_MSG_CHANNEL_OPEN_FAILURE):
                    msgChannelOpenFailure(msg, msglen);
                    break;

                case (byte)(Packets.SSH_MSG_GLOBAL_REQUEST):
                    msgGlobalRequest(msg, msglen);
                    break;

                case (byte)(Packets.SSH_MSG_REQUEST_SUCCESS):
                    msgGlobalSuccess();
                    break;

                case (byte)(Packets.SSH_MSG_REQUEST_FAILURE):
                    msgGlobalFailure();
                    break;

                default:
                    throw new System.IO.IOException("Cannot handle unknown channel message " + (msg[0] & 0xff));

            }
        }
        static ChannelManager()
        {
            log = Logger.getLogger(typeof(ChannelManager));
        }
    }
}